package org.tangerinejunction.imstats.ui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableModel;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.LegendItem;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.axis.SubCategoryAxis;
import org.jfree.chart.labels.CategorySeriesLabelGenerator;
import org.jfree.chart.labels.PieSectionLabelGenerator;
import org.jfree.chart.labels.StandardCategorySeriesLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.category.AbstractCategoryItemRenderer;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.chart.renderer.category.GroupedStackedBarRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.KeyToGroupMap;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.PieDataset;
import org.jfree.data.statistics.BoxAndWhiskerCategoryDataset;
import org.jfree.data.statistics.DefaultBoxAndWhiskerCategoryDataset;
import org.jfree.data.xy.CategoryTableXYDataset;
import org.jfree.data.xy.XYDataset;
import org.tangerinejunction.imstats.data.Account;
import org.tangerinejunction.imstats.data.Entity;
import org.tangerinejunction.imstats.data.IMDataset;
import org.tangerinejunction.imstats.data.TableFactory;
import org.tangerinejunction.imstats.ui.TrendFrame.DataTableModel;
import org.tangerinejunction.imstats.ui.TrendFrame.RankedTableSorter;
import org.tangerinejunction.imstats.util.Constants;
import org.tangerinejunction.imstats.util.Cycle;
import org.tangerinejunction.imstats.util.Increment;
import org.tangerinejunction.imstats.util.TableSorter;
import org.tangerinejunction.imstats.util.TimeRange;

import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.tuple.TupleManager;
import prefuse.data.util.TableIterator;

/**
 * This is part of the new UI.  The TrendFrame helps the user
 * view and analyze trends in variables over the given time range.
 * Right now it's just a stacked chart.
 * 
 * @author meganthorsen
 */
public class CycleFrame extends JFrame implements ActionListener, ComponentListener, MouseListener
{ 
	/**
	 * 
	 */
	private static final long serialVersionUID = 3525615713748551334L;
	
	IMDataset dataset;
	Table accountTable;
	Table entityTable;
	Preferences preferences;
	
	Calendar start;
	Calendar end;
	Cycle cycle;
	int numIncrementColumns;
	JComboBox valueSelector;
 	JComboBox cycleSelector;
 	String viewType;
 	JRadioButton cycleLines;
 	JRadioButton cycleScatter;
 	JRadioButton stackedBar;
 	JRadioButton bucketLines;
 	JRadioButton personLines;
 	JRadioButton cycleBox;
 	JCheckBox normalize;
	
	ChartPanel chartPanel;

 	JTable table;
 	JPopupMenu tableContextMenu;
	
 	/**
 	 * 
 	 * @param dataset				the dataset to analyze
 	 * @param viewType				the view type: Accounts vs. Entities
 	 * @param start					the start time for analysis
 	 * @param end					the end time for analysis
 	 * @param cycle					the cycle to analyze
 	 * @param numIncrementColumns	the number of columns devoted to increments
 	 */
	public CycleFrame(IMDataset dataset, Preferences preferences, String viewType, Calendar start, Calendar end, Cycle cycle, int numIncrementColumns)
	{
		this.dataset = dataset;
		this.preferences = preferences;
		this.viewType = viewType;
		this.accountTable = copyTable(dataset.getAccountTable());
		this.entityTable = copyTable(dataset.getEntityTable());
		this.start = start;
		this.end = end;
		this.cycle = cycle;
		this.numIncrementColumns = numIncrementColumns;
		addComponentListener(this);
		
		createComponents();
		layoutComponents();
		setupEvents();
		setPreferredSize(new Dimension(700, 600));
		setTitle("Cycle");
	}
	
	private static int[] getRows(Table dataTable)
	{
		TableIterator rowIter = dataTable.iterator();
		int[] rows = new int[dataTable.getRowCount()];
		int index = 0;
		while (rowIter.hasNext())
		{
			rows[index] = rowIter.nextInt();
			index++;
		}
		return rows;
	}
	
	private Table copyTable(Table fromTable)
	{
		Table toTable = new Table();
		toTable.setTupleManager(new TupleManager(fromTable, dataset.getGraph(), fromTable.getTuple(0).getClass()));
		for (int i = 0; i < fromTable.getColumnCount(); i++)
			toTable.addColumn(fromTable.getColumnName(i), fromTable.getColumnType(i));
		int[] rows = getRows(fromTable);
		for (int i = 0; i < rows.length; i++)
			toTable.addTuple(fromTable.getTuple(rows[i]));
		return toTable;
	}
	
	private void createComponents()
	{	
		DataTableModel model = new DataTableModel(getCurrentTable());
 		table = new JTable(model);
 		table.setModel(new RankedTableSorter(TableFactory.ROW_COL_NAME, model, table.getTableHeader()));

		Object[] values = {
				TableFactory.FILE_SIZE_COL_NAME, 
				TableFactory.FILE_COUNT_COL_NAME, 
				TableFactory.FILE_DENSITY_COL_NAME };
		valueSelector = new JComboBox(values);
		valueSelector.setSelectedItem(TableFactory.FILE_SIZE_COL_NAME);
	
		chartPanel = new ChartPanel(createChart());

		ButtonGroup group = new ButtonGroup();
	 	cycleLines = new JRadioButton("Cycle line");
	 	cycleScatter = new JRadioButton("Cycle scatter");
	 	stackedBar = new JRadioButton("Bar");
	 	bucketLines = new JRadioButton("Bucket line");
	 	personLines = new JRadioButton("Person line");
	 	cycleBox = new JRadioButton("Cycle box");
	 	group.add(cycleLines);
	 	group.add(cycleScatter);
	 	group.add(stackedBar);
	 	group.add(bucketLines);
	 	group.add(personLines);
	 	group.add(cycleBox);
	 	cycleBox.setSelected(true);

	 	normalize = new JCheckBox("Normalize");
	 	normalize.setSelected(true);
 		
 		cycleSelector = new JComboBox(Constants.cycles);
 		cycleSelector.setSelectedItem(cycle);

 		createTable();
 		
 		tableContextMenu = new JPopupMenu();
 		JMenuItem hideItem = new JMenuItem("Hide selection in chart");
 		hideItem.setAction(new AbstractAction("Hide selection in chart")
 		{
			public void actionPerformed(ActionEvent e) 
			{
				int[] selection = table.getSelectedRows();
				for (int row : selection)
				{
					TableSorter sorter = (TableSorter)table.getModel();
					Tuple t = ((DataTableModel)sorter.getTableModel()).getTuple(sorter.modelIndex(row));
					t.set(Account.FILTERED, true);
				}
				((DataTableModel)((TableSorter)table.getModel()).getTableModel()).fireTableDataChanged();
			}
 		});
 		JMenuItem showItem = new JMenuItem("Show selection in chart");
 		showItem.setAction(new AbstractAction("Show selection in chart")
 		{
			public void actionPerformed(ActionEvent e) 
			{
				int[] selection = table.getSelectedRows();
				for (int row : selection)
				{
					TableSorter sorter = (TableSorter)table.getModel();
					Tuple t = ((DataTableModel)sorter.getTableModel()).getTuple(sorter.modelIndex(row));
					t.set(Account.FILTERED, false);
				}
				((DataTableModel)((TableSorter)table.getModel()).getTableModel()).fireTableDataChanged();
			}
 		});
 		tableContextMenu.add(hideItem);
 		tableContextMenu.add(showItem);
 		tableContextMenu.pack();
	}

	private void layoutComponents()
	{
		JSplitPane content = new JSplitPane();
		setContentPane(content);

 		JPanel datePanel = new JPanel();
 		JLabel panelTitle = new JLabel("Cycle");
 		panelTitle.setFont(panelTitle.getFont().deriveFont(Font.BOLD));

 		JPanel viewPanel = new JPanel(new BorderLayout());
 		viewPanel.add(stackedBar, BorderLayout.NORTH);
 		viewPanel.add(cycleLines, BorderLayout.SOUTH);
 		JPanel viewPanel2 = new JPanel(new BorderLayout());
 		viewPanel2.add(bucketLines, BorderLayout.NORTH);
 		//viewPanel2.add(personLines, BorderLayout.SOUTH);
 		viewPanel2.add(cycleBox, BorderLayout.SOUTH);
 		
 		//datePanel.add(panelTitle);
 		datePanel.add(viewPanel);
 		datePanel.add(viewPanel2);
 		datePanel.add(normalize);
 		datePanel.add(new JLabel("Value: "));
 		datePanel.add(valueSelector);
 		datePanel.add(new JLabel("Cycle: "));
 		datePanel.add(cycleSelector);
 		datePanel.add(Box.createHorizontalStrut(5));
 		
 		JPanel upperPanel = new JPanel(new BorderLayout());
 		upperPanel.add(datePanel, BorderLayout.NORTH);
 		upperPanel.add(chartPanel, BorderLayout.CENTER);
 		JPanel lowerPanel = new JPanel(new BorderLayout());
 		JScrollPane tablePane = new JScrollPane(table);
 		tablePane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
 		lowerPanel.add(tablePane, BorderLayout.CENTER);
 		content.setTopComponent(upperPanel);
 		content.setBottomComponent(lowerPanel);
 		content.setOrientation(JSplitPane.VERTICAL_SPLIT);
 		content.setOneTouchExpandable(true);
 		content.setDividerLocation(1d);
	}
	
	private void setupEvents()
	{
		valueSelector.addActionListener(this);
		cycleSelector.addActionListener(this);
		cycleLines.addActionListener(this);
		cycleScatter.addActionListener(this);
		stackedBar.addActionListener(this);
	 	bucketLines.addActionListener(this);
	 	personLines.addActionListener(this);
	 	cycleBox.addActionListener(this);
	 	normalize.addActionListener(this);
	}
	
	/**
	 * 
	 * @return	the Accounts table if we're looking at Accounts; the Entity table if we're looking at Entities
	 */
	public Table getCurrentTable()
	{
		return viewType.equals(ControlPanel.ACCOUNT_VIEW) ? accountTable : entityTable;
	}
 	
 	private void createTable()
 	{
 		if (accountTable.getColumn(TableFactory.CONSISTENCY_COL_NAME) == null)
 			accountTable.addColumn(TableFactory.CONSISTENCY_COL_NAME, Double.class);
 		if (entityTable.getColumn(TableFactory.CONSISTENCY_COL_NAME) == null)
 			entityTable.addColumn(TableFactory.CONSISTENCY_COL_NAME, Double.class);
		
 		RankedTableSorter sorter = new RankedTableSorter(TableFactory.ROW_COL_NAME, new DataTableModel(getCurrentTable()), table.getTableHeader());
 		table.setModel(sorter);
 		int keyCol = getConsistencyColumn();
 		if (keyCol > 0)
 			sorter.setSortingStatus(keyCol, TableSorter.DESCENDING);
 		/*
 		table.setPreferredSize(new Dimension(sorter.getColumnCount()*80, 300));
 		table.setSize(new Dimension(sorter.getColumnCount()*80, 300));
 		//table.setPreferredScrollableViewportSize(new Dimension(sorter.getColumnCount()*80, 300));
 		table.setMinimumSize(new Dimension(sorter.getColumnCount()*80, 300));
 		table.revalidate();
 		*/
 		
 		table.addMouseListener(this);
 	}
 	
 	private int getConsistencyColumn()
 	{
 		for (int i = 0; i < table.getColumnCount(); i++)
 		{
 			if (table.getColumnName(i).equals(TableFactory.CONSISTENCY_COL_NAME))
 				return i;
 		}
 		return -1;
 	}
	
	private JFreeChart createChart()
	{
		// TODO: fix magic String value
		KeyToGroupMap groupMap = new KeyToGroupMap("Monday");
		final HashMap<Integer, Number> rowTotals = new HashMap<Integer, Number>();
		final HashMap<Integer, Number> rowVariances = new HashMap<Integer, Number>();
		final HashMap<Integer, Number> normVariances = new HashMap<Integer, Number>();
		int keyColIndex = getKeyColIndex();
		Integer[] sortedRows = sortRows(rowTotals, rowVariances, normVariances, keyColIndex);;
		JFreeChart chart = null;
		if (stackedBar != null && stackedBar.isSelected())
		{
			CategoryDataset dataset = createStackedBarDataset(groupMap, sortedRows, keyColIndex, rowTotals);
			chart = ChartFactory.createStackedBarChart("Cycle", getTimeLabel(), (String)valueSelector.getSelectedItem(), dataset, PlotOrientation.VERTICAL, true, true, false);
			CategoryPlot plot = (CategoryPlot)chart.getPlot();
			plot.getDomainAxis().setLowerMargin(0);
			plot.getDomainAxis().setCategoryMargin(0);
			plot.getDomainAxis().setUpperMargin(0);
			GroupedStackedBarRenderer renderer = new GroupedStackedBarRenderer();
			renderer.setSeriesToGroupMap(groupMap);
	        
	        SubCategoryAxis domainAxis = new SubCategoryAxis(getTimeLabel());
	        domainAxis.setCategoryMargin(0.05);
	        
			int numCycles = numIncrementColumns / cycle.getBucketCount();
			if (numIncrementColumns % cycle.getBucketCount() > 0)
				numCycles++;
			for (int i = 1; i <= numCycles; i++)
				domainAxis.addSubCategory(i);
	        plot.setDomainAxis(domainAxis);
	        
			renderer.setBarPainter(new StandardBarPainter());
			renderer.setShadowVisible(false);
			plot.setRenderer(renderer);
	        plot.setFixedLegendItems(createLegendItems(dataset, renderer, sortedRows, keyColIndex, rowTotals));
			plot.setBackgroundPaint(Constants.chartBackground);
		}
		else if (cycleLines != null && cycleLines.isSelected())
		{
			CategoryDataset dataset = createCycleLineDataset(groupMap, sortedRows, keyColIndex, rowTotals);
			chart = ChartFactory.createLineChart("Cycle", getTimeLabel(), (String)valueSelector.getSelectedItem(), dataset, PlotOrientation.VERTICAL, true, true, false);
			//chart = ChartFactory.createBoxAndWhiskerChart("Cycle", getTimeLabel(), (String)valueSelector.getSelectedItem(), dataset, PlotOrientation.VERTICAL, true, true, false);
			//ChartFactory.createStackedAreaChart("Cycle", getTimeLabel(), (String)valueSelector.getSelectedItem(), dataset, PlotOrientation.VERTICAL, true, true, false)
			CategoryPlot plot = (CategoryPlot)chart.getPlot();
			plot.getDomainAxis().setLowerMargin(0);
			plot.getDomainAxis().setCategoryMargin(0);
			plot.getDomainAxis().setUpperMargin(0);
	        plot.setFixedLegendItems(createCycleLegendItems(dataset, plot.getRenderer(), sortedRows, keyColIndex, rowTotals));
	        plot.setBackgroundPaint(Constants.chartBackground);
		}
		else if (cycleScatter != null && cycleScatter.isSelected())
		{
			XYDataset dataset = createCycleScatterDataset(groupMap, sortedRows, keyColIndex, rowTotals);
			chart = ChartFactory.createScatterPlot("Cycle", getTimeLabel(), (String)valueSelector.getSelectedItem(), dataset, PlotOrientation.VERTICAL, true, true, false);
			//ChartFactory.createStackedAreaChart("Cycle", getTimeLabel(), (String)valueSelector.getSelectedItem(), dataset, PlotOrientation.VERTICAL, true, true, false)
			XYPlot plot = (XYPlot)chart.getPlot();
			plot.getDomainAxis().setLowerMargin(0);
			//plot.getDomainAxis().setCategoryMargin(0);
			plot.getDomainAxis().setUpperMargin(0);
	        plot.setFixedLegendItems(createCycleScatterLegendItems(dataset, plot.getRenderer(), sortedRows, keyColIndex, rowTotals));
	        plot.setBackgroundPaint(Constants.chartBackground);
		}
		else if (bucketLines != null && bucketLines.isSelected())
		{
			CategoryDataset dataset = createBucketLineDataset(groupMap, sortedRows, keyColIndex, rowTotals);
			chart = ChartFactory.createLineChart("Cycle", getTimeLabel(), (String)valueSelector.getSelectedItem(), dataset, PlotOrientation.VERTICAL, true, true, false);
			//ChartFactory.createStackedAreaChart("Cycle", getTimeLabel(), (String)valueSelector.getSelectedItem(), dataset, PlotOrientation.VERTICAL, true, true, false)
			CategoryPlot plot = (CategoryPlot)chart.getPlot();
			plot.getDomainAxis().setLowerMargin(0);
			plot.getDomainAxis().setCategoryMargin(0);
			plot.getDomainAxis().setUpperMargin(0);
	        plot.setFixedLegendItems(createBucketLegendItems(dataset, plot.getRenderer(), sortedRows, keyColIndex, rowTotals));
	        plot.setBackgroundPaint(Constants.chartBackground);
		}
		else if (personLines != null && personLines.isSelected())
		{
			CategoryDataset dataset = createPersonLineDataset(groupMap, sortedRows, keyColIndex, rowTotals);
			chart = ChartFactory.createLineChart("Cycle", getTimeLabel(), (String)valueSelector.getSelectedItem(), dataset, PlotOrientation.VERTICAL, true, true, false);
			//ChartFactory.createStackedAreaChart("Cycle", getTimeLabel(), (String)valueSelector.getSelectedItem(), dataset, PlotOrientation.VERTICAL, true, true, false)
			CategoryPlot plot = (CategoryPlot)chart.getPlot();
			plot.getDomainAxis().setLowerMargin(0);
			plot.getDomainAxis().setCategoryMargin(0);
			plot.getDomainAxis().setUpperMargin(0);
	        plot.setFixedLegendItems(createLegendItems(dataset, plot.getRenderer(), sortedRows, keyColIndex, rowTotals));
	        plot.setBackgroundPaint(Constants.chartBackground);
		}
		else if (cycleBox != null && cycleBox.isSelected())
		{
			BoxAndWhiskerCategoryDataset dataset = createCycleBoxDataset(groupMap, sortedRows, keyColIndex, rowTotals);
			chart = ChartFactory.createBoxAndWhiskerChart("Cycle", getTimeLabel(), (String)valueSelector.getSelectedItem(), dataset, true);
			//ChartFactory.createStackedAreaChart("Cycle", getTimeLabel(), (String)valueSelector.getSelectedItem(), dataset, PlotOrientation.VERTICAL, true, true, false)
			CategoryPlot plot = (CategoryPlot)chart.getPlot();
			plot.getDomainAxis().setLowerMargin(0);
			//plot.getDomainAxis().setCategoryMargin(0);
			plot.getDomainAxis().setUpperMargin(0);
	        plot.setFixedLegendItems(createCycleLegendItems(dataset, plot.getRenderer(), sortedRows, keyColIndex, rowTotals));
	        plot.setBackgroundPaint(Constants.chartBackground);
		}
		//for (int i = 0; i < dataset.getRowCount(); i++)
		//	plot.getRenderer().setSeriesPaint(i, Constants.colorPalette[i % 12]);
		return chart;
	}
	
    private LegendItemCollection createLegendItems(CategoryDataset dataset, CategoryItemRenderer renderer, Integer[] sortedRows, int keyColIndex, HashMap<Integer, Number> rowTotals) 
    {
        LegendItemCollection result = new LegendItemCollection();
        int index = 0;
		for (Integer row : sortedRows)
		{
			Tuple t = getCurrentTable().getTuple(row);
			Boolean isFiltered = (Boolean)t.get(Account.FILTERED);
			String key = (String)getCurrentTable().get(row, keyColIndex);
			if (rowTotals.get(row).doubleValue() > 0 && !isFiltered)
			{
				int numCycles = numIncrementColumns / cycle.getBucketCount();
				if (numIncrementColumns % cycle.getBucketCount() > 0)
					numCycles++;
				//if (stackedBar != null && stackedBar.isSelected())
				//{
					Color color = Constants.colorPalette[index % Constants.colorPalette.length];
			        String label = key;
					if (preferences.getLabelOption() == Preferences.LABEL_ONE_CHAR) {
						label = key.substring(0, 1);
					} else if (preferences.getLabelOption() == Preferences.LABEL_SHORT_NAME) {
						label = key.substring(0, Math.min(preferences.getNumLabelChars()-1, label.length()));
					} else {
						label = key;
					}
					/* TODO: add the ordinality option */
					LegendItem item1 = new LegendItem(label, color);
			        result.add(item1);
					for (int i = 1; i <= numCycles; i++)
					{
						if (stackedBar != null && stackedBar.isSelected())
							renderer.setSeriesPaint(dataset.getRowIndex(getCycleKey(i, key)), color);
						else
							renderer.setSeriesPaint(dataset.getRowIndex(key), color);
					}
				/*
				}
				else
				{
					String[] buckets = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
					for (int i = 0; i < buckets.length; i++)
					{
						Color color = Constants.colorPalette[index % Constants.colorPalette.length];
				        LegendItem item1 = new LegendItem(key, color);
				        result.add(item1);
						renderer.setSeriesPaint(dataset.getRowIndex(key), color);
						//renderer.setSeriesPaint(dataset.getRowIndex(buckets[i]), color);
					}
				}
				*/
				index++;
			}
		}
        return result;
    }
	
    private LegendItemCollection createBucketLegendItems(CategoryDataset dataset, CategoryItemRenderer renderer, Integer[] sortedRows, int keyColIndex, HashMap<Integer, Number> rowTotals) 
    {
    	//String[] buckets = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
        LegendItemCollection result = new LegendItemCollection();
		for (int i = 0; i < dataset.getRowCount(); i++)
		{
			String bucketName = (String)dataset.getRowKey(i);
			Color color = Constants.colorPalette[i % Constants.colorPalette.length];
	        LegendItem item1 = new LegendItem(bucketName, color);
	        result.add(item1);
	        renderer.setSeriesPaint(dataset.getRowIndex(bucketName), color);
		}
        return result;
    }
	
    private LegendItemCollection createCycleLegendItems(CategoryDataset dataset, CategoryItemRenderer renderer, Integer[] sortedRows, int keyColIndex, HashMap<Integer, Number> rowTotals) 
    {
    	LegendItemCollection result = new LegendItemCollection();
		for (int i = 0; i < dataset.getRowCount(); i++)
		{
			String bucketName = (String)dataset.getRowKey(i);
			Color color = Constants.colorPalette[i % Constants.colorPalette.length];
	        LegendItem item1 = new LegendItem(bucketName, color);
	        result.add(item1);
	        renderer.setSeriesPaint(dataset.getRowIndex(bucketName), color);
		}
        return result;
    }
	
    private LegendItemCollection createCycleScatterLegendItems(XYDataset dataset, XYItemRenderer renderer, Integer[] sortedRows, int keyColIndex, HashMap<Integer, Number> rowTotals) 
    {
    	LegendItemCollection result = new LegendItemCollection();
		for (int i = 0; i < dataset.getSeriesCount(); i++)
		{
			String bucketName = (String)dataset.getSeriesKey(i);
			Color color = Constants.colorPalette[i % Constants.colorPalette.length];
	        LegendItem item1 = new LegendItem(bucketName, color);
	        result.add(item1);
	        renderer.setSeriesPaint(i, color);
		}
        return result;
    }
	
	protected String getTimeLabel()
	{
		return new TimeRange(start, end).toString() + " by " + cycle.toString();
	}
	
	private int getKeyColIndex()
	{
		for (int i = 0; i < getCurrentTable().getColumnCount(); i++)
			if (getCurrentTable().getColumnName(i).equals(Account.NAME_KEY) ||
					getCurrentTable().getColumnName(i).equals(Entity.NAME_KEY))
				return i;
		return -1;
	}
	
	private CategoryDataset createStackedBarDataset(KeyToGroupMap groupMap, Integer[] sortedRows, int keyColIndex, HashMap<Integer, Number> rowTotals)
	{
		DefaultCategoryDataset dataset = new DefaultCategoryDataset();
		for (int col = getCurrentTable().getColumnCount()-numIncrementColumns; col < getCurrentTable().getColumnCount(); col++)
		{
			String colName = getCurrentTable().getColumnName(col);
			for (Integer row : sortedRows)
			{
				String key = (String)getCurrentTable().get(row, keyColIndex);
				Number value = (Number)getCurrentTable().get(row, col);
				if (rowTotals.get(row).doubleValue() > 0 && TimeRange.isLabelString(colName))
				{
					int cycleNumber = (col - (getCurrentTable().getColumnCount()-numIncrementColumns)) / cycle.getBucketCount() + 1;
					groupMap.mapKeyToGroup(getCycleKey(cycleNumber, key), "Cycle " + cycleNumber);
					//dataset.addValue(value, getCycleKey(cycleNumber, key), parseBucketName(colName));
					Tuple t = getCurrentTable().getTuple(row);
					Boolean isFiltered = (Boolean)t.get(Account.FILTERED);
					if (!isFiltered)
						dataset.addValue(value, getCycleKey(cycleNumber, key), parseBucketName(colName));
				}
			}
		}
		return dataset;
	}
	
	private CategoryTableXYDataset createCycleScatterDataset(KeyToGroupMap groupMap, Integer[] sortedRows, int keyColIndex, HashMap<Integer, Number> rowTotals)
	{
		CategoryTableXYDataset dataset = new CategoryTableXYDataset();
		for (int col = getCurrentTable().getColumnCount()-numIncrementColumns; col < getCurrentTable().getColumnCount(); col++)
		{
			String colName = getCurrentTable().getColumnName(col);
			int cycleNumber = (col - (getCurrentTable().getColumnCount()-numIncrementColumns)) / cycle.getBucketCount() + 1;
			String cycleKey = "Cycle " + cycleNumber;
			HashMap<String, Double> totals = new HashMap<String, Double>();
			for (Integer row : sortedRows)
			{
				String key = (String)getCurrentTable().get(row, keyColIndex);
				Double value = ((Number)getCurrentTable().get(row, col)).doubleValue();
				String bucketName = parseBucketName(colName);
				Tuple t = getCurrentTable().getTuple(row);
				Boolean isFiltered = (Boolean)t.get(Account.FILTERED);
				if (!isFiltered && rowTotals.get(row).doubleValue() > 0)
				{
					if (totals.containsKey(bucketName))
						totals.put(bucketName, totals.get(bucketName) + value);
						//totals.put(bucketName, totals.get(bucketName) + ((value > 0) ? Math.log(value) : value));
					else
						totals.put(bucketName, value);
						//totals.put(bucketName, (value > 0) ? Math.log(value) : 0);
				}
			}
			Iterator bucketNames = totals.keySet().iterator();
			while (bucketNames.hasNext())
			{
				String bucketName = (String)bucketNames.next();
				int bucketNumber = getBucketNumber(bucketName);
				//dataset.addValue(totals.get(bucketName), key, bucketName);
				//dataset.addValue(totals.get(bucketName), cycleKey, bucketName);
				dataset.add(bucketNumber, totals.get(bucketName), cycleKey);
			}
		}
		return dataset;
	}
	
	private CategoryDataset createCycleLineDataset(KeyToGroupMap groupMap, Integer[] sortedRows, int keyColIndex, HashMap<Integer, Number> rowTotals)
	{
		DefaultCategoryDataset dataset = new DefaultCategoryDataset();
		for (int col = getCurrentTable().getColumnCount()-numIncrementColumns; col < getCurrentTable().getColumnCount(); col++)
		{
			String colName = getCurrentTable().getColumnName(col);
			int cycleNumber = (col - (getCurrentTable().getColumnCount()-numIncrementColumns)) / cycle.getBucketCount() + 1;
			String cycleKey = "Cycle " + cycleNumber;
			HashMap<String, Double> totals = new HashMap<String, Double>();
			for (Integer row : sortedRows)
			{
				String key = (String)getCurrentTable().get(row, keyColIndex);
				Double value = ((Number)getCurrentTable().get(row, col)).doubleValue();
				String bucketName = parseBucketName(colName);
				Tuple t = getCurrentTable().getTuple(row);
				Boolean isFiltered = (Boolean)t.get(Account.FILTERED);
				if (!isFiltered && rowTotals.get(row).doubleValue() > 0)
				{
					if (totals.containsKey(bucketName))
						totals.put(bucketName, totals.get(bucketName) + value);
						//totals.put(bucketName, totals.get(bucketName) + ((value > 0) ? Math.log(value) : value));
					else
						totals.put(bucketName, value);
						//totals.put(bucketName, (value > 0) ? Math.log(value) : 0);
				}
			}
			Iterator bucketNames = totals.keySet().iterator();
			while (bucketNames.hasNext())
			{
				String bucketName = (String)bucketNames.next();
				//dataset.addValue(totals.get(bucketName), key, bucketName);
				dataset.addValue(totals.get(bucketName), cycleKey, bucketName);
			}
		}
		return dataset;
	}
	
	private BoxAndWhiskerCategoryDataset createCycleBoxDataset(KeyToGroupMap groupMap, Integer[] sortedRows, int keyColIndex, HashMap<Integer, Number> rowTotals)
	{
		DefaultBoxAndWhiskerCategoryDataset dataset = new DefaultBoxAndWhiskerCategoryDataset();
		HashMap<String, ArrayList<Double>> boxValues = new HashMap<String, ArrayList<Double>>();
		HashMap<Integer, Double> cycleTotals = new HashMap<Integer, Double>();
		for (int col = getCurrentTable().getColumnCount()-numIncrementColumns; col < getCurrentTable().getColumnCount(); col++)
		{
			String colName = getCurrentTable().getColumnName(col);
			int cycleNumber = (col - (getCurrentTable().getColumnCount()-numIncrementColumns)) / cycle.getBucketCount() + 1;
			HashMap<String, Double> totals = new HashMap<String, Double>();
			for (Integer row : sortedRows)
			{
				Double value = ((Number)getCurrentTable().get(row, col)).doubleValue();
				String bucketName = parseBucketName(colName);
				Tuple t = getCurrentTable().getTuple(row);
				Boolean isFiltered = (Boolean)t.get(Account.FILTERED);
				if (!isFiltered && rowTotals.get(row).doubleValue() > 0)
				{
					if (totals.containsKey(bucketName))
						totals.put(bucketName, totals.get(bucketName) + value);
					else
						totals.put(bucketName, value);
				}
			}
			for (String bucketName : cycle.getBucketNames())
			{
				if (cycleTotals.containsKey(cycleNumber) && totals.get(bucketName) != null)
					cycleTotals.put(cycleNumber, cycleTotals.get(cycleNumber).doubleValue() + totals.get(bucketName));
				else if (!cycleTotals.containsKey(cycleNumber))
					cycleTotals.put(cycleNumber, totals.get(bucketName) == null ? 0.0 : totals.get(bucketName));
					
				if (boxValues.containsKey(bucketName))
					boxValues.get(bucketName).add(totals.get(bucketName));
				else
				{
					ArrayList<Double> newList = new ArrayList<Double>();
					newList.add(totals.get(bucketName));
					boxValues.put(bucketName, newList);
				}
			}
		}
		for (String bucketName : cycle.getBucketNames())
		{
			if (boxValues.get(bucketName) != null)
			{
				ArrayList<Double> bucketValues = boxValues.get(bucketName);

				if (normalize.isSelected())
				{
					for (int i = 0; i < bucketValues.size(); i++)
						if (bucketValues.get(i) != null)
							bucketValues.set(i, bucketValues.get(i)/
									cycleTotals.get(i/cycle.getBucketCount() + 1));
				}
				dataset.add(bucketValues, bucketName, " ");
			}
		}
		return dataset;
	}
	
	private CategoryDataset createBucketLineDataset(KeyToGroupMap groupMap, Integer[] sortedRows, int keyColIndex, HashMap<Integer, Number> rowTotals)
	{
		DefaultCategoryDataset dataset = new DefaultCategoryDataset();
		for (int col = getCurrentTable().getColumnCount()-numIncrementColumns; col < getCurrentTable().getColumnCount(); col++)
		{
			String colName = getCurrentTable().getColumnName(col);
			int cycleNumber = (col - (getCurrentTable().getColumnCount()-numIncrementColumns)) / cycle.getBucketCount() + 1;
			String cycleKey = "Cycle " + cycleNumber;
			HashMap<String, Double> totals = new HashMap<String, Double>();
			for (Integer row : sortedRows)
			{
				String key = (String)getCurrentTable().get(row, keyColIndex);
				Double value = ((Number)getCurrentTable().get(row, col)).doubleValue();
				String bucketName = parseBucketName(colName);
				Tuple t = getCurrentTable().getTuple(row);
				Boolean isFiltered = (Boolean)t.get(Account.FILTERED);
				if (!isFiltered && rowTotals.get(row).doubleValue() > 0)
				{
					if (totals.containsKey(bucketName))
						totals.put(bucketName, totals.get(bucketName) + value);
						//totals.put(bucketName, totals.get(bucketName) + ((value > 0) ? Math.log(value) : value));
					else
						totals.put(bucketName, value);
						//totals.put(bucketName, (value > 0) ? Math.log(value) : 0);
				}
			}
			Iterator bucketNames = totals.keySet().iterator();
			while (bucketNames.hasNext())
			{
				String bucketName = (String)bucketNames.next();
				//dataset.addValue(totals.get(bucketName), key, bucketName);
				dataset.addValue(totals.get(bucketName), bucketName, cycleKey);
			}
		}
		return dataset;
	}
	
	private CategoryDataset createPersonLineDataset(KeyToGroupMap groupMap, Integer[] sortedRows, int keyColIndex, HashMap<Integer, Number> rowTotals)
	{
		DefaultCategoryDataset dataset = new DefaultCategoryDataset();
		for (Integer row : sortedRows)
		{
			String key = (String)getCurrentTable().get(row, keyColIndex);
			HashMap<String, Double> totals = new HashMap<String, Double>();
			for (int col = getCurrentTable().getColumnCount()-numIncrementColumns; col < getCurrentTable().getColumnCount(); col++)
			{
				String colName = getCurrentTable().getColumnName(col);
				Double value = ((Number)getCurrentTable().get(row, col)).doubleValue();
				String bucketName = parseBucketName(colName);
				Tuple t = getCurrentTable().getTuple(row);
				Boolean isFiltered = (Boolean)t.get(Account.FILTERED);
				if (!isFiltered && rowTotals.get(row).doubleValue() > 0)
				{
					if (totals.containsKey(bucketName))
						totals.put(bucketName, totals.get(bucketName) + value);
						//totals.put(bucketName, totals.get(bucketName) + ((value > 0) ? Math.log(value) : value));
					else
						totals.put(bucketName, value);
						//totals.put(bucketName, (value > 0) ? Math.log(value) : 0);
				}
			}
			Iterator bucketNames = totals.keySet().iterator();
			while (bucketNames.hasNext())
			{
				String bucketName = (String)bucketNames.next();
				dataset.addValue(totals.get(bucketName), key, bucketName);
			}
		}
		return dataset;
	}
	
	private String getCycleKey(int cycleNumber, String key)
	{
		return "Cycle " + cycleNumber + "(" + key + ")";
	}
	
	private Integer[] sortRows(HashMap<Integer, Number> rowTotals, HashMap<Integer, Number> rowVariances, HashMap<Integer, Number> normVariances, int keyColIndex)
	{
		// Sort these in some interesting way:
		// * descending total of values for key
		// * ascending standard deviation/variance of values for key 
		// * normalize the variance calculation by the key's total value to obtain a consistency measure
		TableIterator rowIter = getCurrentTable().iterator();
		int[] rows = new int[getCurrentTable().getRowCount()];
		int index = 0;
		while (rowIter.hasNext())
		{
			rows[index] = rowIter.nextInt();
			index++;
		}
		for (int i = 0; i < rows.length; i++)
		{
			int row = rows[i];
			String key = (String)getCurrentTable().get(row, keyColIndex);
			Double total = 0d;
			int n = 0;
			double mean = 0;
			double M2 = 0;
			for (int col = getCurrentTable().getColumnCount()-numIncrementColumns; col < getCurrentTable().getColumnCount(); col++)
			{
				n++;
				Number value = (Number)getCurrentTable().get(row, col);
				String colName = getCurrentTable().getColumnName(col);
				total = total + value.doubleValue();
				double delta = value.doubleValue() - mean;
				mean = mean + delta/n;
				M2 = M2 + delta*(value.doubleValue() - mean);
			}
			int normN = 0;
			double normMean = 0;
			double normM2 = 0;
			for (int col = getCurrentTable().getColumnCount()-numIncrementColumns; col < getCurrentTable().getColumnCount(); col++)
			{
				normN++;
				Number value = (Number)getCurrentTable().get(row, col);
				String colName = getCurrentTable().getColumnName(col);
				total = total + value.doubleValue();
				double delta = value.doubleValue()/total - normMean;
				normMean = normMean + delta/normN;
				normM2 = normM2 + delta*(value.doubleValue()/total - normMean);
			}
			rowTotals.put(new Integer(row), total);
			Double var = new Double(M2/(n-1));
			rowVariances.put(new Integer(row), var.isNaN() ? new Double(0) : var);
			Double normVar = new Double(normM2/(normN-1));
			normVariances.put(new Integer(row), normVar.isNaN() ? new Double(0) : normVar);
		}
		//final HashMap<Integer, Number> secondarySort = rowTotals;
		final HashMap<Integer, Number> primarySort = rowTotals;
		Integer[] sortedRows = new Integer[primarySort.size()];
		sortedRows = primarySort.keySet().toArray(sortedRows);
		/*
		Arrays.sort(sortedRows, new Comparator<Integer>()
				{
					public int compare(Integer o1, Integer o2) 
					{
						Number total1 = secondarySort.get(o1);
						Number total2 = secondarySort.get(o2);
						if (total1 == total2)
							return 0;
						else if (total1.doubleValue() > total2.doubleValue())
							return -1;
						else 
							return 1;
					}
				});
		*/
		Arrays.sort(sortedRows, new Comparator<Integer>()
		{
			public int compare(Integer o1, Integer o2) 
			{
				Number total1 = primarySort.get(o1);
				Number total2 = primarySort.get(o2);
				if (total1 == total2)
					return 0;
				else if (total1.doubleValue() < total2.doubleValue())
					return 1;
				else 
					return -1;
			}
		});
		
		return sortedRows;
	}
	
	private String parseBucketName(String bucketName)
	{
		TimeRange range = TimeRange.parseLabelString(bucketName, cycle.getBucketSize());
		if (cycle.equals(Cycle.DAY_OF_WEEK))
		{
			int day = (range.getBeginning().get(Calendar.DAY_OF_WEEK));
			switch (day)
			{
				case Calendar.MONDAY: return "Monday";
				case Calendar.TUESDAY: return "Tuesday";
				case Calendar.WEDNESDAY: return "Wednesday";
				case Calendar.THURSDAY: return "Thursday";
				case Calendar.FRIDAY: return "Friday";
				case Calendar.SATURDAY: return "Saturday";
				case Calendar.SUNDAY: return "Sunday";
			}
		}
		else if (cycle.equals(Cycle.MONTH_OF_YEAR))
		{
			int month = (range.getBeginning().get(Calendar.MONTH));
			switch (month)
			{
				case Calendar.JANUARY: return "January";
				case Calendar.FEBRUARY: return "February";
				case Calendar.MARCH: return "March";
				case Calendar.APRIL: return "April";
				case Calendar.MAY: return "May";
				case Calendar.JUNE: return "June";
				case Calendar.JULY: return "July";
				case Calendar.AUGUST: return "August";
				case Calendar.SEPTEMBER: return "September";
				case Calendar.OCTOBER: return "October";
				case Calendar.NOVEMBER: return "November";
				case Calendar.DECEMBER: return "December";
			}
		}
		return bucketName;
	}
	
	private int getBucketNumber(String bucketName)
	{
		if (cycle.equals(Cycle.DAY_OF_WEEK))
		{
			if (bucketName.equals("Monday")) return Calendar.MONDAY;
			else if (bucketName.equals("Tuesday")) return Calendar.TUESDAY;
			else if (bucketName.equals("Wednesday")) return Calendar.WEDNESDAY;
			else if (bucketName.equals("Thursday")) return Calendar.THURSDAY;
			else if (bucketName.equals("Friday")) return Calendar.FRIDAY;
			else if (bucketName.equals("Saturday")) return Calendar.SATURDAY;
			else if (bucketName.equals("Sunday")) return Calendar.SUNDAY;
		}
		else if (cycle.equals(Cycle.MONTH_OF_YEAR))
		{
			if (bucketName.equals("January")) return Calendar.JANUARY;
			else if (bucketName.equals("February")) return Calendar.FEBRUARY;
			else if (bucketName.equals("March")) return Calendar.MARCH;
			else if (bucketName.equals("April")) return Calendar.APRIL;
			else if (bucketName.equals("May")) return Calendar.MAY;
			else if (bucketName.equals("June")) return Calendar.JUNE;
			else if (bucketName.equals("July")) return Calendar.JULY;
			else if (bucketName.equals("August")) return Calendar.AUGUST;
			else if (bucketName.equals("September")) return Calendar.SEPTEMBER;
			else if (bucketName.equals("October")) return Calendar.OCTOBER;
			else if (bucketName.equals("November")) return Calendar.NOVEMBER;
			else if (bucketName.equals("December")) return Calendar.DECEMBER;
		}
		return -1;
	}
	
	/**
	 * Update the data table and chart.
	 * 
	 * @param dataTable	the data table
	 */
	public void updateDataTable(Table dataTable)
	{
		if (dataTable == getCurrentTable())
			;
		else if (dataTable == dataset.getAccountTable())
			viewType = ControlPanel.ACCOUNT_VIEW;
		else
			viewType = ControlPanel.ENTITY_VIEW;
		//this.dataTable = dataTable;
		RankedTableSorter sorter = (RankedTableSorter)table.getModel();
		((DataTableModel)sorter.getTableModel()).data = dataTable;
		updateChart();
		updateTable();
	}
	
	/**
	 * Update the data table and chart.
	 * 
	 * @param dataTable				the data table
	 * @param start					the start time for analysis
	 * @param end					the end time for analysis
	 * @param cycle					the cycle for analysis
	 * @param numIncrementColumns	the number of columns devoted to increments
	 */
	public void updateDataTable(Table dataTable, Calendar start, Calendar end, Cycle cycle, int numIncrementColumns)
	{
		if (dataTable == getCurrentTable())
			;
		else if (dataTable == dataset.getAccountTable())
			viewType = ControlPanel.ACCOUNT_VIEW;
		else
			viewType = ControlPanel.ENTITY_VIEW;
		//this.dataTable = dataTable;
		RankedTableSorter sorter = (RankedTableSorter)table.getModel();
		((DataTableModel)sorter.getTableModel()).data = dataTable;
		this.start = start;
		this.end = end;
		this.cycle = cycle;
		this.numIncrementColumns = numIncrementColumns;
		updateChart();
		updateTable();
	}
 	
	/**
	 * Update the data table.
	 */
 	public void updateTable()
 	{
 		RankedTableSorter sorter = (RankedTableSorter)table.getModel();
 		sorter.fireTableDataChanged();
 		sorter.fireTableStructureChanged();
 		for (int i = 0; i < sorter.getColumnCount(); i++)
 			if (sorter.getSortingStatus(i) != TableSorter.NOT_SORTED)
 				sorter.setSortingStatus(i, sorter.getSortingStatus(i));
 		table.updateUI();
 		/*
 		table.setPreferredSize(new Dimension(sorter.getColumnCount()*80, 300));
 		table.setSize(new Dimension(sorter.getColumnCount()*80, 300));
 		//table.setPreferredScrollableViewportSize(new Dimension(sorter.getColumnCount()*80, 300));
 		table.setMinimumSize(new Dimension(sorter.getColumnCount()*80, 300));
 		table.revalidate();
 		*/
 	}
	
 	/**
 	 * Update the chart.
 	 */
	public void updateChart()
	{
		chartPanel.setChart(createChart());
		chartPanel.repaint();
	}

	public void actionPerformed(ActionEvent e) 
	{
		removeIncrementColumns();
		addIncrementColumns();
	}

	/**
	 * Add any increment columns to the table.
	 */
 	public void addIncrementColumns()
 	{
 		if (entityTable.getColumn(TableFactory.CONSISTENCY_COL_NAME) == null)
 			entityTable.addColumn(TableFactory.CONSISTENCY_COL_NAME, Double.class);
 		if (accountTable.getColumn(TableFactory.CONSISTENCY_COL_NAME) == null)
 			accountTable.addColumn(TableFactory.CONSISTENCY_COL_NAME, Double.class);
		
 		String valueColumnName = (String)valueSelector.getSelectedItem();
 		Cycle cycle = (Cycle)cycleSelector.getSelectedItem();

 		Calendar startCopy = (Calendar)start.clone();
 		Calendar mid = (Calendar)start.clone();
		while (mid.before(end))
		{
 			mid.add(Calendar.DATE, cycle.getBucketSize().numDays);
 			mid.add(Calendar.MONTH, cycle.getBucketSize().numMonths);
 			mid.add(Calendar.YEAR, cycle.getBucketSize().numYears);
 			TableFactory.addColumnToTable(valueColumnName, entityTable, new TimeRange(startCopy, mid), true);
 			TableFactory.addColumnToTable(valueColumnName, accountTable, new TimeRange(startCopy, mid), true);
 			startCopy.add(Calendar.DATE, cycle.getBucketSize().numDays);
 			startCopy.add(Calendar.MONTH, cycle.getBucketSize().numMonths);
 			startCopy.add(Calendar.YEAR, cycle.getBucketSize().numYears);
			numIncrementColumns++;
		}
		TableFactory.calculateConsistencyColumn(entityTable, valueColumnName,
				entityTable.getColumnCount()-numIncrementColumns, entityTable.getColumnCount());
		TableFactory.calculateConsistencyColumn(accountTable, valueColumnName,
				accountTable.getColumnCount()-numIncrementColumns, accountTable.getColumnCount());
 		
 		updateDataTable(getCurrentTable(), start, 
 				end, cycle, numIncrementColumns);
 	}
 	
 	/**
 	 * Remove any increment columns from the table.
 	 */
 	public void removeIncrementColumns()
 	{
 		int colCount = entityTable.getColumnCount();
 		for (int i = colCount-1; i >= colCount-numIncrementColumns; --i)
 			entityTable.removeColumn(entityTable.getColumn(i));
 		colCount = accountTable.getColumnCount();
 		for (int i = colCount-1; i >= colCount-numIncrementColumns; --i)
 			accountTable.removeColumn(accountTable.getColumn(i));
 		numIncrementColumns = 0;
 	}
 	
 	/**
 	 * The model for the data table display
 	 * 
 	 * @author meganthorsen
 	 */
 	protected class DataTableModel extends AbstractTableModel
 	{
 		private static final long serialVersionUID = 132940759456976493L;
 		
 		private final String[] ignoreColumns = 
 		{
 			Account.LOG_FILES_KEY,
 			TableFactory.FILE_COUNT_COL_NAME,
 			TableFactory.NORM_FILE_COUNT_COL_NAME,
 			TableFactory.FILE_SIZE_COL_NAME,
 			TableFactory.NORM_FILE_SIZE_COL_NAME,
 			TableFactory.FILE_DENSITY_COL_NAME,
 			TableFactory.NORM_FILE_DENSITY_COL_NAME,
 			TableFactory.FREQUENT_LENGTH_COL_NAME,

            Account.MIN_LOG_DATE,
            TableFactory.CONVERSATIONS_PER_DAY_NAME,
            TableFactory.DAYS_BETWEEN_CONVERSATION_NAME,
            TableFactory.MAX_DAYS_BETWEEN_NAME,
            TableFactory.PROB_I_STARTED_NAME,
            TableFactory.SIZE_I_STARTED_NAME,
            TableFactory.STARTED_TIMES_VOLUME_NAME,
            TableFactory.NUM_DAYS_IN_DATASET_NAME,
 		};
 		
 		Table data;
 		
 		public DataTableModel(Table data)
 		{
 			this.data = data;
 		}
 		
 		public int getColumnCount() 
 		{
 			int numCols = data.getColumnCount();
 			for (String colName : ignoreColumns)
 				if (data.getColumnNumber(colName) >= 0)
 					numCols--;
 			return numCols;
 			/*
 			if (data.getColumnNumber(Account.LOG_FILES_KEY) < 0)
 				return data.getColumnCount();
 			return data.getColumnCount()-1;
 			*/
 		}

 		public int getRowCount() 
 		{
 			return data.getRowCount(); 
 		}
 		
 		private int getDataColumn(int col)
 		{
 			int tableCol = 0;
 			int index = 0;
 			while (index <= col && tableCol < data.getColumnCount())
 			{
 				final String colName = data.getColumnName(tableCol);
 				int ignoreIndex = -1;
 	 			for (int i = 0; i < ignoreColumns.length; i++)
 	 			{
 	 				if (ignoreColumns[i].equals(colName))
 	 					ignoreIndex = i;
 	 			}
 				if (ignoreIndex < 0)
 					index++;
 				tableCol++;
 			}
 			return tableCol-1;
 			/*
 			if (col < data.getColumnNumber(Account.LOG_FILES_KEY) ||
 					!data.canGet(Account.LOG_FILES_KEY, ArrayList.class))
 				return col;
 			else
 				return col+1;
			*/
 		}

 		public Object getValueAt(int row, int col) 
 		{
 			TableIterator rowIter = data.iterator();
 			int dataRow = -1;
 			int index = 0;
 			while (rowIter.hasNext())
 			{
 				int i = rowIter.nextInt();
 				if (index == row)
 				{
 					dataRow = i;
 					break;
 				}
 				index++;
 			}
 			int column = getDataColumn(col);
 			if (data.getColumnName(column).equals(Account.ENTITY_KEY))
 				return ((Entity)data.get(dataRow, column)).getName();
 			else if (data.getColumnName(column).equals(Entity.ACCOUNTS_KEY))
 			{
 				if (data.getTuple(dataRow) instanceof Entity)
 				{
	 				Entity entity = (Entity)data.getTuple(dataRow);
	 				return entity.getAccounts().size();
 				}
 				else
 					return 1;
 			}
 			//if (data.getColumnName(col).equals(Account.LOG_FILES_KEY))
 			//	return ((ArrayList)data.get(row, col)).size();
 			if (data.getColumnName(getDataColumn(col)).equals(Account.FILTERED))
 				return (Boolean)getTuple(row).get(Account.FILTERED);
 			return data.get(dataRow, column);
 		}
 		
 		public Tuple getTuple(int row)
 		{
 			return data.getTuple(row);
 		}
 		
 		public String getColumnName(int col)
 		{
 			return data.getColumnName(getDataColumn(col));
 		}
 		
 		public Class<?> getColumnClass(int col)
 		{
 			int column = getDataColumn(col);
 			if (data.getColumnName(column).equals(Account.ENTITY_KEY))
 				return String.class;
 			if (data.getColumnName(column).equals(Entity.ACCOUNTS_KEY))
 				return Integer.class;
 			//if (data.getColumnName(col).equals(Account.LOG_FILES_KEY))
 			//	return Integer.class;
 			return data.getColumnType(column);
 		}
 		
 		public boolean isCellEditable(int row, int col)
 		{
 			if (data.getColumnName(getDataColumn(col)).equals(Account.ENTITY_KEY))
 				return true;
 			else if (data.getColumnName(getDataColumn(col)).equals(Account.FILTERED))
 				return true;
 			else
 				return false;
 		}

 		public void setValueAt(Object value, int row, int col)
 		{
 			if (data.getColumnName(getDataColumn(col)).equals(Account.ENTITY_KEY))
 			{
 				Account account = (Account)data.getTuple(row);
 				dataset.addAccountToEntityByName((String)value, account);
 			}
 			else if (data.getColumnName(getDataColumn(col)).equals(Account.FILTERED))
 			{
 				Tuple t = getTuple(row);
 				t.set(Account.FILTERED, value);
 			}
 		}
 	}
 	
 	protected class RankedTableSorter extends TableSorter
 	{
 		private static final long serialVersionUID = 4963425141701216502L;
 		
 		private String rankColName;
 		
 		public RankedTableSorter(String rankColName)
 		{
 			super();
 			this.rankColName = rankColName;
 		}
 		
 		public RankedTableSorter(String rankColName, TableModel model)
 		{
 			super(model);
 			this.rankColName = rankColName;
 		}
 		
 		public RankedTableSorter(String rankColName, TableModel model, JTableHeader header)
 		{
 			super(model, header);
 			this.rankColName = rankColName;
 		}

 	    public Object getValueAt(int row, int column) 
 	    {
 	    	if (tableModel.getColumnName(column).equals(rankColName))
 	    		return row+1;
 	        return tableModel.getValueAt(modelIndex(row), column);
 	    }
 	}

	public void componentHidden(ComponentEvent e) {}

	public void componentMoved(ComponentEvent e) {}

	public void componentResized(ComponentEvent e)
	{
		((JSplitPane)getContentPane()).setDividerLocation(1d);
	}

	public void componentShown(ComponentEvent e) {}

	public void mouseClicked(MouseEvent e) {}

	public void mouseEntered(MouseEvent e) {}

	public void mouseExited(MouseEvent e) {}

	public void mousePressed(MouseEvent e) 
	{
		tableContextMenu.setVisible(false);
		if (e.isPopupTrigger())
		{
			JSplitPane pane = (JSplitPane)getContentPane();
			tableContextMenu.show(this, e.getX(), e.getY() + pane.getDividerLocation() + tableContextMenu.getHeight());
		}
	}

	public void mouseReleased(MouseEvent e) {}
}